# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 x6
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2017 宝塔软件(http://bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 梁凯强 <1249648969@qq.com>
# +-------------------------------------------------------------------
# +--------------------------------------------------------------------
# |   宝塔webshell 内置扫描
# +--------------------------------------------------------------------

import public,hashlib,os,sys,json,time,re
import send_mail

class webshell_check:
    __PATH = '/www/server/panel/data/'
    __mail_config = '/www/server/panel/data/stmp_mail.json'
    __mail_list_data = '/www/server/panel/data/mail_list.json'
    __dingding_config = '/www/server/panel/data/dingding.json'
    __mail_list = []
    __weixin_user = []

    def __init__(self):
        self.mail = send_mail.send_mail()
        if not os.path.exists(self.__mail_list_data):
            ret = []
            public.writeFile(self.__mail_list_data, json.dumps(ret))
        else:
            try:
                mail_data = json.loads(public.ReadFile(self.__mail_list_data))
                self.__mail_list = mail_data
            except:
                ret = []
                public.writeFile(self.__mail_list_data, json.dumps(ret))
        if not os.path.exists(self.__PATH+'webshell_rule.json'):
            rule=["@\\$\\_\\(\\$\\_", "\\$\\_=\"\"", "\\${'\\_'", "@preg\\_replace\\((\")*\\/(\\S)*\\/e(\")*,\\$_POST\\[\\S*\\]", "base64\\_decode\\(\\$\\_", "'e'\\.'v'\\.'a'\\.'l'", "\"e\"\\.\"v\"\\.\"a\"\\.\"l\"", "\"e\"\\.\"v\"\\.\"a\"\\.\"l\"", "\\$(\\w)+\\(\"\\/(\\S)+\\/e", "\\(array\\)\\$_(POST|GET|REQUEST|COOKIE)", "\\$(\\w)+\\(\\${", "@\\$\\_=", "\\$\\_=\\$\\_", "chr\\((\\d)+\\)\\.chr\\((\\d)+\\)", "phpjm\\.net", "cha88\\.cn", "c99shell", "phpspy", "Scanners", "cmd\\.php", "str_rot13", "webshell", "EgY_SpIdEr", "tools88\\.com", "SECFORCE", "eval\\(('|\")\\?>", "preg_replace\\(\"\\/\\.\\*\\/e\"", "assert\\(('|\"|\\s*)\\$", "eval\\(gzinflate\\(", "gzinflate\\(base64_decode\\(", "eval\\(base64_decode\\(", "eval\\(gzuncompress\\(", "ies\",gzuncompress\\(\\$", "eval\\(gzdecode\\(", "eval\\(str_rot13\\(", "gzuncompress\\(base64_decode\\(", "base64_decode\\(gzuncompress\\(", "eval\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "assert\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "require\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "require_once\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "include\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "include_once\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "call_user_func\\((\"|')assert(\"|')", "call_user_func\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "\\$_(POST|GET|REQUEST|COOKIE)\\[([^\\]]+)\\]\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)\\[", "echo\\(file_get_contents\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "file_put_contents\\(('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)\\[([^\\]]+)\\],('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)", "fputs\\(fopen\\((.+),('|\")w('|\")\\),('|\"|\\s*)\\$_(POST|GET|REQUEST|COOKIE)\\[", "SetHandlerapplication\\/x-httpd-php", "php_valueauto_prepend_file", "php_valueauto_append_file"]
            public.WriteFile(self.__PATH+'webshell_rule.json',json.dumps(rule))

    # 返回配置邮件地址
    def return_mail_list(self):
        return self.__mail_list

    # 查看自定义邮箱配置
    def get_user_mail(self):
        qq_mail_info = json.loads(public.ReadFile(self.__mail_config))
        if len(qq_mail_info) == 0:
            return False
        return qq_mail_info
    # 查看钉钉
    def get_dingding(self):
        qq_mail_info = json.loads(public.ReadFile(self.__dingding_config))
        if len(qq_mail_info) == 0:
            return False
        return qq_mail_info

    # 查看能使用的告警通道
    def get_settings(self):
        qq_mail_info = json.loads(public.ReadFile(self.__mail_config))
        if len(qq_mail_info) == 0:
            user_mail = False
        else:
            user_mail = True
        dingding_info = json.loads(public.ReadFile(self.__dingding_config))
        if len(dingding_info) == 0:
            dingding = False
        else:
            dingding = True
        ret = {}
        ret['user_mail'] = {"user_name": user_mail, "mail_list": self.__mail_list,"info":self.get_user_mail()}
        ret['dingding'] = {"dingding": dingding,"info":self.get_dingding()}
        return ret

    # 返回站点
    def return_site(self):
        data = public.M('sites').field('name,path').select()
        ret = {}
        for i in data:
            ret[i['name']] = i['path']
        return public.returnMsg(True, ret)

    # 获取规则
    def get_rule(self):
        ret = []
        if os.path.exists(self.__PATH + 'webshell_rule.json'):
            try:
                data = json.loads(public.ReadFile(self.__PATH + 'webshell_rule.json'))
                return data
            except:
                return False
        else:
            return False

    def get_dir(self,path):
        return_data=[]
        data2=[]
        [[return_data.append(os.path.join(root, file)) for file in files] for root, dirs, files in os.walk(path)]
        for i in return_data:
            if str(i.lower())[-4:] == '.php':
                data2.append(i)
        return data2

    # 目录
    def getdir_list(self, path_data):
        if os.path.exists(str(path_data)):
            return self.get_dir(path_data)
        else:
            return False

    # 扫描
    def scan(self, filelist, rule):
        import time
        time_data = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
        ret = []
        data2=[]
        for file in filelist:
            data = open(file).read()
            for r in rule:
                if re.compile(r).findall(data):
                    result = {}
                    result[file] = r
                    ret.append(result)
                    data = ("%s [!] %s %s  \n" % (time_data, file, r))
                    data2.append(file)
        return data2

    #上传webshell
    def upload_shell(self,data):
        if len(data)==0:return []
        return_data=[]
        for i in data:
            if self.upload_file_url(i):
                return_data.append(i)
        return return_data

    def san_dir(self, path,send='mail'):
        file = self.getdir_list(path)
        if not file: return exit( "目录不存在")
        rule = self.get_rule()
        if not rule: return exit("规则为空或者规则文件错误")
        result = self.scan(file, rule)
        return_data=self.upload_shell(result)
        tongdao=self.get_settings()
        if len(return_data)>=1:
            if send=='dingding':
                if tongdao['dingding']:
                    msg="webshell查杀发现%s目录中存在木马如下:%s"%(path,return_data)
                    self.mail.dingding_send(msg)
            elif send=='mail':
                if tongdao['user_mail']:
                	
                    title = "webshell查杀发现%s目录中存在木马如下" % (path)
                    body = "webshell查杀发现%s目录中存在木马如下:%s" % (path, return_data)
                    if len(self.__mail_list)==0:
            			if tongdao['user_mail']['user_name']:
                        		self.mail.qq_smtp_send(str(tongdao['user_mail']['info']['qq_mail']), title=title, body=body)
                    else:
                        for i in self.__mail_list:
                        	if tongdao['user_mail']['user_name']:
                            		self.mail.qq_smtp_send(str(i),title=title,body=body)
        return return_data

    def send_san_dir(self, path,send):
        file = self.getdir_list(path)
        if not file: return exit( "目录不存在")
        rule = self.get_rule()
        if not rule: return exit("规则为空或者规则文件错误")
        result = self.scan(file, rule)
        return_data=self.upload_shell(result)
        tongdao=self.get_settings()
        if len(return_data)>=1:
            if send=='dingding':
                if tongdao['dingding']:
                    msg="webshell查杀发现%s目录中存在木马如下:%s"%(path,return_data)
                    self.mail.dingding_send(msg)
            elif send=='mail':
                if tongdao['user_mail']:
                    title = "webshell查杀发现%s目录中存在木马如下" % (path)
                    body = "webshell查杀发现%s目录中存在木马如下:%s" % (path, return_data)
                    if len(self.__mail_list)==0:
                        self.mail.qq_smtp_send(str(tongdao['user_mail']['info']['qq_mail']), title=title, body=body)
                    else:
                        for i in self.__mail_list:
                            self.mail.qq_smtp_send(str(i),title=title,body=body)
        return return_data

    def upload_file_url(self, filename):
        try:
            if os.path.exists(filename):
                data = public.ExecShell(
                    '/usr/local/curl/bin/curl https://scanner.baidu.com/enqueue -F archive=@%s' % filename)
                data = json.loads(data[0])
                time.sleep(3)
                import http_requests
                default_headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36'
                }
                data_list = http_requests.get(url=data['url'], headers=default_headers, verify=False)
                data2 = data_list.json()
                if 'data' in data2[0]:
                    if len(data2[0]['data']) >= 1:
                        if 'descr' in data2[0]['data'][0]:
                            if 'WebShell' in data2[0]['data'][0]['descr']:
                                print('%s文件为木马'%filename)
                                self.send_baota2(filename)
                                return True
                return False
            else:
                return False
        except:
            return False

    def read_file_md5(self, filename):
        if os.path.exists(filename):
            with open(filename, 'rb') as fp:
                data = fp.read()
            file_md5 = hashlib.md5(data).hexdigest()
            return file_md5
        else:
            return False

    def send_baota2(self, filename):
        cloudUrl = 'http://www.bt.cn/api/panel/btwaf_submit'
        pdata = {'codetxt': public.ReadFile(filename), 'md5': self.read_file_md5(filename), 'type': '0',
                 'host_ip': public.GetLocalIp(), 'size': os.path.getsize(filename)}
        ret = public.httpPost(cloudUrl, pdata)
        return True

    def send_baota(self, filename):
        if not os.path.exists(filename): return False
        cloudUrl = 'http://www.bt.cn/api/panel/btwaf_submit'
        pdata = {'codetxt': public.ReadFile(filename), 'md5': self.read_file_md5(filename), 'type': '0',
                 'host_ip': public.GetLocalIp(), 'size': os.path.getsize(filename)}
        ret = public.httpPost(cloudUrl, pdata)
        if ret == '1':
            return self.check_webshell(filename)
        elif ret == '-1':
            return self.check_webshell(filename)
        else:
            return False

    def check_webshell(self, filename):
        if not os.path.exists(filename): return False
        cloudUrl = 'http://www.bt.cn/api/panel/btwaf_check_file'
        pdata = {'md5': self.read_file_md5(filename), 'size': os.path.getsize(filename)}
        ret = public.httpPost(cloudUrl, pdata)
        if ret == '0':
            return False
        elif ret == '1':
            return False
        elif ret == '-1':
            return False
        else:
            return False

    def __get_md5(self, s):
        m = hashlib.md5()
        m.update(s.encode('utf-8'))
        return m.hexdigest()

if __name__ == "__main__":
    public.WriteFile('/www/server/panel/data/webshell_data.json', json.dumps([]))
    type=sys.argv[1]
    path=sys.argv[2]
    send=sys.argv[3]
    os.chdir("/www/server/panel")
    import sys
    sys.path.append('class')
    import public
    aa = webshell_check()
    if type=='dir':
        data=aa.san_dir(path)
        public.WriteFile('/www/server/panel/data/webshell_data.json',json.dumps(data))
        if len(data)>=1:
            public.WriteLog('宝塔内置webshell查杀','查杀%s目录查到如下木马%s'%(path,data))
    elif type=='site':
        data = public.M('sites').where('name=?', (path,)).field('name,id,path').select()
        if len(data)>=1:
            path=data[0]['path']
            ##木马返回在这里
            data = aa.send_san_dir(path,send)
            if len(data) >= 1:
                public.WriteLog('宝塔内置webshell查杀', '查杀%s目录查到如下木马%s' % (path, data))


